﻿using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;
using System.Printing;
using System.Text;
using System.Threading;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.IO;
using System.Windows.Threading;
using Lime49.Exceptions;
using Lime49.UI;
using LockCrypt.Core;
using LockCrypt.DAL;
using LockCrypt.Firefox;
using LockCrypt.Properties;
using LockCrypt.DAL.Exporters;

namespace LockCrypt {
    /// <summary>
    /// The main application window.
    /// </summary>
    public partial class WinMain : Window {
        private BackgroundWorker worker;
        private Point _lastMouseDown = new Point(0, 0);
        private bool _isDragging = false;
        private DispatcherTimer _relockTimer;
        private System.Timers.Timer _delayTimer;
        private int _lockDelay = int.MaxValue;
        private delegate void FilterDelegate();

        public static readonly DependencyProperty DatabasePathProperty = DependencyProperty.Register("DatabasePath", typeof(string), typeof(WinMain));
        /// <summary>
        /// Gets or sets the current database path.
        /// </summary>
        /// <value>The current database path.</value>
        public string DatabasePath {
            get { return base.GetValue(DatabasePathProperty).ToString(); }
            set { base.SetValue(DatabasePathProperty, value); }
        }

        /// <summary>
        /// Gets or sets the cached list of top level accounts and groups.
        /// </summary>
        /// <value>The cached list of top level accounts and groups.</value>
        private IEnumerable<Account> Accounts { get;set;}

        private IEnumerable<Group> Groups { get; set; }

        /// <summary>
        /// Gets or sets the list of cached templates.
        /// </summary>
        /// <value>The list of cached templates..</value>
        private IEnumerable<ITemplate> Templates {
            get { return (this.Resources["templateSource"] as CollectionViewSource).Source as IEnumerable<ITemplate>; }
            set {
                var templateSource = this.Resources["templateSource"] as CollectionViewSource;
                templateSource.Source = value;
            }
        }

        public WinMain() {
            InitializeComponent();
            _relockTimer = new DispatcherTimer(DispatcherPriority.ApplicationIdle, Application.Current.Dispatcher);
            _relockTimer.Interval = TimeSpan.FromSeconds(1);
            _relockTimer.Tick += new EventHandler(_relockTimer_Tick);
            _delayTimer = new System.Timers.Timer();
            _delayTimer.Elapsed += new System.Timers.ElapsedEventHandler(SearchTimerElapsed);
            string[] filters = new[] {
                I18nUtils.GetString("Strings", "Contains"),
                I18nUtils.GetString("Strings", "StartsWith"),
                I18nUtils.GetString("Strings", "EndsWith"),
            };
            var filterTypeSource = this.Resources["filterTypeSource"] as CollectionViewSource;
            filterTypeSource.Source = filters;
            FirefoxServer.Instance.AccountAdded += (s, e) => {
                Application.Current.Dispatcher.Invoke((DispatcherPriority.Normal), new Action(() => NavigationCommands.Refresh.Execute(null, this)));
            };
        }

        /// <summary>
        /// Initializes the window and loads the database after checking whether this is the first time the application 
        /// has been run and showing a setup wizard installer if necessary..
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Window_Loaded(object sender, RoutedEventArgs e) {
            if(DateTime.Now.Month == 12 && DateTime.Now.Day >= 20 && DateTime.Now.Day <= 25) { // woot!
                WinFollow f = new WinFollow();
                f.Owner = this;
                f.ShowDialog();
            }
            if(Settings.Default.FirstRun) {
                WinInstaller dlg = new WinInstaller { Owner = this };
                dlg.ShowDialog();
            } else if(Settings.Default.AutoOpenOnStartup && !DalConfigurationManager.Provider.CanAttemptConnection) {
                LockCryptCommands.CreateDatabase.Execute(DalConfigurationManager.Provider, this);
            }
            UpdateDbLocation();
            ReloadSettings();
                NavigationCommands.Refresh.Execute(null, this);
            AddShortcutKeys();
        }

        /// <summary>
        /// Adds InputBindings for shortcut keys.
        /// </summary>
        private void AddShortcutKeys() {
            var gestureConverter = new KeyGestureConverter();
            try {
                KeyGesture autoTypeGesture = (KeyGesture)gestureConverter.ConvertFromInvariantString(Settings.Default.AutoTypeHotkey);
                InputBindings.Add(new InputBinding(LockCryptCommands.AutoType, autoTypeGesture));
            } catch {
                Console.WriteLine(string.Format("Shortcut key {0} not valid for autotype", Settings.Default.AutoTypeHotkey));
            }
            try {
                KeyGesture relockGesture = (KeyGesture)gestureConverter.ConvertFromInvariantString(Settings.Default.RelockHotkey);
                InputBindings.Add(new InputBinding(LockCryptCommands.Relock, relockGesture));
            } catch {
                Console.WriteLine(string.Format("Shortcut key {0} not valid for relock", Settings.Default.RelockHotkey));
            }
        }

        #region Accounts
        /// <summary>
        /// Shows a dialog to add an account.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AddAccount(object sender, ExecutedRoutedEventArgs e) {
            string parentId = null;
            if((e.Parameter ?? string.Empty).ToString() == "context") { // context menu click
                if(treAccounts.SelectedItem is IGroup) {
                    // use the clicked group's ID
                    parentId = (treAccounts.SelectedItem as IGroup).ID;
                }
                if(treAccounts.SelectedItem is IAccount) {
                    // use the clicked account's parent ID
                    parentId = (treAccounts.SelectedItem as IAccount).ParentID;
                } // otherwise add to root group
            } else if(treAccounts.SelectedItem is IGroup) {
                parentId = (treAccounts.SelectedItem as IGroup).ID;
            }
            WinAddAccount dlg = string.IsNullOrEmpty(parentId) ? new WinAddAccount() : new WinAddAccount(parentId);
            dlg.Owner = this;
            if(dlg.ShowDialog() == true) {
                IAccount newAccount = dlg.Account;
                AddAccounts(new[] { newAccount });
            }
        }

        /// <summary>
        /// Adds accounts asynchronously to the database.
        /// </summary>
        /// <param name="newAccounts">The accounts to add.</param>
        private void AddAccounts(IEnumerable<IAccount> newAccounts) {
            WaitForWorker();
            worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.AddAccounts(newAccounts, null);
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "ErrorSaving"));
                }
                NavigationCommands.Refresh.Execute(null, this);
            };
            worker.RunWorkerAsync();
            WaitForWorker();
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Updates an account in the database asynchronously.
        /// </summary>
        /// <param name="account">The account to update.</param>
        private void ShowEditAccount(IAccount account) {
            WinAddAccount dlg = new WinAddAccount(account.Clone()) { Owner = this };
            if(dlg.ShowDialog() == true) {
                IAccount acc = dlg.Account;
                WaitForWorker();
                worker = new BackgroundWorker();
                worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.EditAccount(acc);
                worker.RunWorkerCompleted += (s, rwe) => {
                    if(rwe.Error != null) {
                        DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "ErrorSavingFmt", rwe.Error.Message), I18nUtils.GetString("Errors", "Error"));
                    }
                    NavigationCommands.Refresh.Execute(null, this);
                };
                worker.RunWorkerAsync();
                WaitForWorker();
                CommandManager.InvalidateRequerySuggested();
            }
        }

        /// <summary>
        /// Confirms deletion of an account.
        /// </summary>
        /// <param name="account">The account.</param>
        private void DeleteAccount(IAccount account) {
            if(DialogBox.Show(this, I18nUtils.GetString("Strings", "ConfirmDeleteAccountFmt", account.Name), I18nUtils.GetString("Strings", "DeleteAccount"), DialogBox.DialogBoxType.YesNo, DialogBox.DialogBoxIcon.Question, DialogBoxButton.No) == DialogBoxButton.Yes) {
                WaitForWorker();
                worker = new BackgroundWorker();
                worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.DeleteAccount(account.ID);
                worker.RunWorkerCompleted += (s, rwe) => {
                    SetStatus(null);
                    if(rwe.Error != null) {
                        DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                    } else {
                        NavigationCommands.Refresh.Execute(null, this);
                    }
                };
                worker.RunWorkerAsync();
                WaitForWorker();
                CommandManager.InvalidateRequerySuggested();
            }
        }

        /// <summary>
        /// Creates a duplicate of an account.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CreateCopy(object sender, ExecutedRoutedEventArgs e) {
            IAccount selectedAccount = (e.Parameter as IAccount) ?? (treAccounts.SelectedItem as IAccount);
            if(selectedAccount != null) {
                IAccount clone = selectedAccount.Clone();
                clone.ID = Utils.GenID(true);
                clone.Name = I18nUtils.GetString("Strings", "CopyFmt", selectedAccount.Name);
                AddAccounts(new[] { clone });
            }
        }

        /// <summary>
        /// Sends the configured AutoType commands for the selected account.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AutoType(object sender, ExecutedRoutedEventArgs e) {
            IAccount selectedAccount = null;
            if(e.Parameter != null) {
                selectedAccount = e.Parameter as IAccount;
            } else {
                if(accountView.IsKeyboardFocusWithin) {
                    selectedAccount = accountView.SelectedItems.FirstOrDefault() as IAccount;
                }
                if(selectedAccount == null && treAccounts.IsKeyboardFocusWithin) {
                    // either the account view is not focused, or a group is selected
                    selectedAccount = treAccounts.SelectedItem as IAccount;
                }
            }
            if(selectedAccount == null)
                return;
            if(string.IsNullOrEmpty(selectedAccount.AutoTypeConfig.TargetWindow) || !selectedAccount.AutoTypeConfig.Commands.Any())
                DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "AutoTypeNotConfigured"), I18nUtils.GetString("Errors", "Error"));
            try {
                AutoTypeRecorder.Playback(this, selectedAccount.AutoTypeConfig, selectedAccount.Fields);
            } catch(Exception ex) {
                DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Errors", "Error"));
            }
        }
        #endregion

        #region Groups
        /// <summary>
        /// Determines whether the 'Add group' command can be executed by the sender.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanAddGroup(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && DalConfigurationManager.Provider.IsConnected && (e.Parameter == null || ((e.Parameter ?? string.Empty).ToString() == "context" && treAccounts.SelectedItem is IGroup));
        }

        /// <summary>
        /// Shows a dialog to add a new group.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AddGroup(object sender, ExecutedRoutedEventArgs e) {
            Group selectedGroup = treAccounts.SelectedItem as Group;
            WinAddGroup dlg = new WinAddGroup(selectedGroup == null ? null : selectedGroup.ID) { Owner = this };
            if(dlg.ShowDialog() == true) {
                IGroup group = dlg.CurrentGroup;
                WaitForWorker();
                worker = new BackgroundWorker();
                worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.AddGroups(new[] { group }, null);
                worker.RunWorkerCompleted += (s, rwe) => {
                    SetStatus(null);
                    if(rwe.Error != null) {
                        DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "ErrorSaving"));
                    } else {
                        NavigationCommands.Refresh.Execute(null, this);
                    }
                };
                worker.RunWorkerAsync();
            }
        }

        /// <summary>
        /// Shows the 'Edit Group' dialog.
        /// </summary>
        /// <param name="group">The group to edit.</param>
        private void ShowEditGroup(IGroup group) {
            WinAddGroup dlg = new WinAddGroup(group) { Owner = this };
            if(dlg.ShowDialog() == true) {
                IGroup newGroup = dlg.CurrentGroup.Clone();
                WaitForWorker();
                worker = new BackgroundWorker();
                worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.EditGroup(newGroup);
                worker.RunWorkerCompleted += (s, rwe) => {
                    SetStatus(null);
                    if(rwe.Error != null) {
                        DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "ErrorSaving"));
                    } else {
                        NavigationCommands.Refresh.Execute(null, this);
                    }
                };
                worker.RunWorkerAsync();
            }
        }

        /// <summary>
        /// Confirms deletion of a group.
        /// </summary>
        /// <param name="group">The group.</param>
        private void DeleteGroup(IGroup group) {
            if(DialogBox.Show(this, I18nUtils.GetString("Strings", "DeleteGroupDesc"), I18nUtils.GetString("Strings", "DeleteGroup"), DialogBox.DialogBoxType.YesNo, DialogBox.DialogBoxIcon.Question, DialogBoxButton.No) == DialogBoxButton.Yes) {
                WaitForWorker();
                worker = new BackgroundWorker();
                worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.DeleteGroup(group);
                worker.RunWorkerCompleted += (s, rwe) => {
                    SetStatus(null);
                    if(rwe.Error != null) {
                        DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                    } else {
                        NavigationCommands.Refresh.Execute(null, this);
                    }
                };
                worker.RunWorkerAsync();
                WaitForWorker();
                CommandManager.InvalidateRequerySuggested();
            }
        }
        #endregion

        /// <summary>
        /// Shows a dialog to edit an account or group, depending on the CommandParameter and selected tree item.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void EditItem(object sender, ExecutedRoutedEventArgs e) {
            if((e.Parameter ?? string.Empty).ToString() == "-1" && treAccounts.SelectedItem is INestable) {
                if(treAccounts.SelectedItem is Account) {
                    ShowEditAccount(treAccounts.SelectedItem as Account);
                } else if(treAccounts.SelectedItem is Group) {
                    ShowEditGroup(treAccounts.SelectedItem as Group);
                }
            } else {
                if(e.Parameter is Account) {
                    ShowEditAccount(e.Parameter as Account);
                } else if(e.Parameter is Group) {
                    ShowEditGroup(e.Parameter as Group);
                }
            }
        }

        /// <summary>
        /// Confirms deletion of an account or group, depending on the CommandParameter and selected tree item.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void DeleteItem(object sender, ExecutedRoutedEventArgs e) {
           if(accountView.IsKeyboardFocusWithin) {
               INestable[] selectedItems = accountView.SelectedItems.ToArray();
               if(selectedItems.Length == 0) {
                   return;
               }
               if(DialogBox.Show(this, I18nUtils.GetString("Strings", "ConfirmDeleteItemsChildrenFmt", selectedItems.Length.ToString()), I18nUtils.GetString("Strings", "ConfirmDeletion"), DialogBox.DialogBoxType.YesNo, DialogBox.DialogBoxIcon.Question, DialogBoxButton.No) == DialogBoxButton.Yes) {
                   WaitForWorker();
                   worker = new BackgroundWorker();
                   worker.DoWork += (s, dwe) => {
                       IEnumerable<string> selectedGroupIds = selectedItems.Where(n => n is IGroup).Select(g => g.ID);
                       var allGroups = DalConfigurationManager.Provider.GetGroups();
                       IEnumerable<IGroup> nestedGroups = LockCryptBLL.ArrangeGroups(allGroups);
                       List<string> groupIdsToDelete = new List<string>();
                       foreach(var nestedGroup in nestedGroups) {
                           // search top level groups
                           foreach(string groupIdToDelete in selectedGroupIds) {
                               IGroup groupToDelete = nestedGroup.FindGroup(groupIdToDelete); // find in heirarchy
                               if(groupToDelete != null) {
                                   IEnumerable<IGroup> unavailableGroups = groupToDelete.Flatten();
                                   groupIdsToDelete.AddRange(unavailableGroups.Select(g => g.ID));
                               }
                           }
                       }
                       DalConfigurationManager.Provider.DeleteGroups(groupIdsToDelete);
                       DalConfigurationManager.Provider.DeleteAccounts(selectedItems.Where(n => n is IAccount).Select(a => a.ID));
                   };
                   worker.RunWorkerCompleted += (s, rwe) => {
                       SetStatus(null);
                       if(rwe.Error != null) {
                           DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "ErrorSaving"));
                       } else {
                           NavigationCommands.Refresh.Execute(null, this);
                       }
                   };
                   worker.RunWorkerAsync();
               }
           } else if(treAccounts.IsKeyboardFocusWithin && treAccounts.SelectedItem is INestable) {
                if(treAccounts.SelectedItem is Account) {
                    DeleteAccount(treAccounts.SelectedItem as Account);
                } else if(treAccounts.SelectedItem is Group) {
                    DeleteGroup(treAccounts.SelectedItem as Group);
                }
            } else {
                if(e.Parameter is Account) {
                    DeleteAccount(e.Parameter as Account);
                } else if(e.Parameter is Group) {
                    DeleteGroup(e.Parameter as Group);
                }
            }
        }

        #region AutoLock
        /// <summary>
        /// Locks the database if the delay has elapsed.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void _relockTimer_Tick(object sender, EventArgs e) {
            try {
                int inactiveSeconds = InactivityTimer.GetLastInputTime();
                if(inactiveSeconds > _lockDelay)
                    LockCryptCommands.Relock.Execute(null, this);
            } catch(Exception ex) {
                Console.WriteLine("Error getting last activity time: {0}", ex.Message);
            }
        }

        /// <summary>
        /// Enables the automatic re-lock timer, if configured.
        /// </summary>
        private void ConfigureLockTimer() {
            _lockDelay = Settings.Default.AutoLockDelay;
            if(Settings.Default.AutoLock) {
                if(_relockTimer.IsEnabled) {
                    _relockTimer.Stop();
                }
                _relockTimer.Start();
            } else {
                _relockTimer.Stop();
            }
        }
        #endregion

        #region Drag and Drop (TreeView <-> AccountView)
        /// <summary>
        /// Determines whether a dragged item is an account or group which can be dropped.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.DragEventArgs"/> instance containing the event data.</param>
        private void tre_DragEnter(object sender, DragEventArgs e) {
            if(e.Data.GetDataPresent(typeof(INestable))) {
                if(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
                    e.Effects = DragDropEffects.Copy;
                } else {
                    e.Effects = DragDropEffects.Move;
                }
            } else {
                e.Effects = DragDropEffects.None;
            }
        }

        /// <summary>
        /// Stores the mouse position on draggable controls to allow for MinimumHorizontalDragDistance or MinimumVerticalDragDistance.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void tre_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e) {
            _lastMouseDown = e.GetPosition(null);
        }

        /// <summary>
        /// Starts dragging when items are dragged from the treeview.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseEventArgs"/> instance containing the event data.</param>
        private void tre_MouseMove(object sender,MouseEventArgs e) {
            TreeView tree = treAccounts;// sender as TreeView;
            if(tree != null && !_isDragging && e.LeftButton == MouseButtonState.Pressed && treAccounts.SelectedItem != null) {
                Point position = e.GetPosition(null);
                if(Math.Abs(position.X - _lastMouseDown.X) > SystemParameters.MinimumHorizontalDragDistance || Math.Abs(position.Y - _lastMouseDown.Y) > SystemParameters.MinimumVerticalDragDistance) {
                    _isDragging = true;
                    var effect = DragDrop.DoDragDrop(tree, tree.SelectedItem, DragDropEffects.Move | DragDropEffects.Copy);
                    _isDragging = false;
                }
            }
        }

        /// <summary>
        /// Handles drop events on the TreeView.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.DragEventArgs"/> instance containing the event data.</param>
        private void tre_Drop(object sender, DragEventArgs e) {
            try {
                INestable dropTarget = Lime49.WPF.Utils.GetItemAtLocation<INestable>(treAccounts, e.GetPosition(treAccounts));
                string newParentId = null;
                if(dropTarget is IAccount) {
                    newParentId = (dropTarget as IAccount).ParentID;
                } else if(dropTarget is IGroup) {
                    newParentId = (dropTarget as IGroup).ID;
                }
                WaitForWorker();
                worker = new BackgroundWorker();
                if(e.Data.GetDataPresent(typeof(Account))) { // no, IAccount won't work...
                    Account droppedAccount = e.Data.GetData(typeof(Account)) as Account;
                    droppedAccount.ParentID = newParentId;
                    if(Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl)) {
                        droppedAccount.ID = Utils.GenID(true);
                        droppedAccount.Name = I18nUtils.GetString("Strings", "CopyFmt", droppedAccount.Name);
                    }
                    worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.EditAccount(droppedAccount);
                } else if(e.Data.GetDataPresent(typeof(Group))) {
                    Group droppedGroup = e.Data.GetData(typeof(Group)) as Group;
                    // check for not dragging a parent inside it's child, or a descendant of a child
                    var childGroups = droppedGroup.Flatten().OfType<IGroup>();
                    var unavailableIds = childGroups.Select(g => g.ID);
                    if(unavailableIds.Contains(newParentId)) {
                        return;
                    }
                    droppedGroup.ParentID = newParentId;
                    worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.EditGroup(droppedGroup);
                } else {
                    return;
                }
                worker.RunWorkerCompleted += (s, rwe) => {
                    if(rwe.Error != null) {
                        DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "ErrorSaving"));
                    }
                    NavigationCommands.Refresh.Execute(null, this);
                };
                worker.RunWorkerAsync();
            } catch(Exception ex) {
                DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Errors", "Error"));
            }
        }
        #endregion

        #region Misc. dialogs
        /// <summary>
        /// Checks for, then optionally installs a new version.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CheckForNewVersion(object sender, ExecutedRoutedEventArgs e) {
            WinUpdater dlg = new WinUpdater() { Owner = this };
            dlg.ShowDialog();
        }

        /// <summary>
        /// Shows the 'About LockCrypt' dialog.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowAbout(object sender, ExecutedRoutedEventArgs e) {
            WinAbout dlg = new WinAbout() { Owner = this };
            dlg.ShowDialog();
        }

        /// <summary>
        /// Shows the 'Options' dialog.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowOptions(object sender, ExecutedRoutedEventArgs e) {
            WinOptions dlg = new WinOptions() { Owner = this };
            dlg.ShowDialog();
            ReloadSettings();
            NavigationCommands.Refresh.Execute(null, this);
        }

        /// <summary>
        /// Shows the password generator.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowPasswordGenerator(object sender, ExecutedRoutedEventArgs e) {
            WinPasswordGenerator dlg = new WinPasswordGenerator() { Owner = this };
            dlg.Show();
        }

        /// <summary>
        /// Shows the 'Manage Templates' dialog.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowTemplates(object sender, ExecutedRoutedEventArgs e) {
            WinTemplates dlg = new WinTemplates() { Owner = this };
            dlg.ShowDialog();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void Print(object sender, RoutedEventArgs e) {
            if(treAccounts.SelectedItem == null) { // print all accounts
                DialogBox.ShowAlert(this, I18nUtils.GetString("Errors", "SelectWhichItemsToInclude"), I18nUtils.GetString("Errors", "Error"));
            } else if(treAccounts.SelectedItem is Group) {
                PrintGroup((Group) treAccounts.SelectedItem);
            } else if(treAccounts.SelectedItem is Account) {
                PrintAccount(treAccounts.SelectedItem as Account);
            }
            /*switch(DialogBox.Show(this,I18nUtils.GetString("Strings", "SelectWhichItemsToInclude"),I18nUtils.GetString("Strings", "ChooseItems"), DialogBox.DialogBoxIcon.Question, 
                   DialogBoxButton.Custom2, I18nUtils.GetString("Strings", "SelectedGroup"), I18nUtils.GetString("Strings", "SelectedGroupContents"), I18nUtils.GetString("Strings", "AllAccounts"))) {
                case DialogBoxButton.Custom1: // selected group
                    PrintGroup((Group)treAccounts.SelectedItem);
                    break;
                case DialogBoxButton.Custom2: // contents of selected group
                    IEnumerable<Group> selectedGroupWithChildren = (treAccounts.SelectedItem as Group).Flatten().Cast<Group>();
                    IEnumerable<IAccount> accounts = selectedGroupWithChildren.SelectMany(g => g.Accounts);
                    PrintAccounts(accounts);
                    break;
                case DialogBoxButton.Custom3: // all accounts
                    PrintAccounts(this.Accounts.Cast<IAccount>());
                    break;
            }*/
        }

        /// <summary>
        /// Prints a single account.
        /// </summary>
        /// <param name="account">The account.</param>
        private void PrintAccount(Account account) {
            PrintDialog dlg = new PrintDialog();
            if(dlg.ShowDialog() == true) {
                int margin = 5;
                Size pageSize = new Size(dlg.PrintableAreaWidth - margin * 2, dlg.PrintableAreaHeight - margin * 2);
                FlowDocument document = Lime49.WPF.Utils.LoadFlowDocument(System.IO.Path.Combine(Utils.GetApplicationPath(), "PrintView.xaml"), account);
                LockCryptWPFBLL.AddAccountToDocument(document, account);
                IDocumentPaginatorSource paginator = document as IDocumentPaginatorSource;
                paginator.DocumentPaginator.PageSize = pageSize;
                dlg.PrintDocument(paginator.DocumentPaginator, account.Name);
            }
        }

        /// <summary>
        /// Prints a group.
        /// </summary>
        /// <param name="group">The group.</param>
        private void PrintGroup(IGroup group) {
            PrintDialog dlg = new PrintDialog();
            if(dlg.ShowDialog() == true) {
                int margin = 5;
                Size pageSize = new Size(dlg.PrintableAreaWidth - margin * 2, dlg.PrintableAreaHeight - margin * 2);
                FlowDocument document = Lime49.WPF.Utils.LoadFlowDocument(System.IO.Path.Combine(Utils.GetApplicationPath(), "PrintView.xaml"), group);
                LockCryptWPFBLL.AddGroupToDocument(document, group);
                IDocumentPaginatorSource paginator = document as IDocumentPaginatorSource;
                paginator.DocumentPaginator.PageSize = pageSize;
                dlg.PrintDocument(paginator.DocumentPaginator, group.Name);
            }
        }

        /*private void PrintAccounts(IEnumerable<IAccount> accounts) {//Dictionary<Group,IEnumerable<Account>> groupedAccounts
            PrintDialog dlg = new PrintDialog();
            if(dlg.ShowDialog() == true) {
                var groupIdMap = this.Groups.ToDictionary(g => g.ID); // <group ID, group>
                var dummyGroup = new Group() { Name = I18nUtils.GetString("Strings", "None") };
                var groupedAccounts = accounts.GroupBy(a => a.ParentID ?? "-1")
                                              .ToDictionary(a => a.Key != "-1" && groupIdMap.ContainsKey(a.Key) ? groupIdMap[a.Key] : dummyGroup, g => g.ToList());
                int margin = 5;
                Size pageSize = new Size(dlg.PrintableAreaWidth - margin * 2, dlg.PrintableAreaHeight - margin * 2);
                FlowDocument document = WPFUtils.LoadFlowDocument(System.IO.Path.Combine(Utils.GetApplicationPath(), "PrintView.xaml"), groupedAccounts);
                //List accountList = LockCryptWPFBLL.GenerateAccountList(groupedAccounts);
                //document.Blocks.Add(accountList);
                DocumentPaginator paginator = WPFUtils.GetPagedDocument(document, pageSize);
                paginator.PageSize = pageSize;
                dlg.PrintDocument(paginator, I18nUtils.GetString("Strings", "LockCryptAccounts"));
                //Window1 w = new Window1(document) { Owner = this };
                //w.Show();
            }
        }*/

        #endregion

        #region Database import/export/CRUD
        /// <summary>
        /// Shows an open dialog, then imports or opens a database.
        /// This is only for LockCrypt databases (of all types).
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void OpenDatabase(object sender, ExecutedRoutedEventArgs e) {
            Constants.DatabaseType openType = string.IsNullOrEmpty(Convert.ToString(e.Parameter)) ? Constants.DatabaseType.XML : (Constants.DatabaseType)Enum.Parse(typeof(Constants.DatabaseType), e.Parameter.ToString(), true);
            EncryptionProviders openAlgorithm;
            if(DalConfigurationManager.Provider != null && DalConfigurationManager.Provider.Encryption != null) {
                openAlgorithm = DalConfigurationManager.Provider.Encryption.EncryptionAlgorithm; // just use the current algorithm
            } else {
                openAlgorithm = EncryptionProviders.Aes256;
            }
            WinOpenDatabase dlg = new WinOpenDatabase(openType, openAlgorithm) { Owner = this };
            if(dlg.ShowDialog() == true) {
                IDbProvider newProvider = dlg.Provider;
                switch (dlg.OpenAction) {
                    case WinOpenDatabase.FileImportAction.Open:
                        DalConfigurationManager.Provider = newProvider;
                        NavigationCommands.Refresh.Execute(null, this);
                        break;
                    case WinOpenDatabase.FileImportAction.Merge:
                    default:
                        WaitForWorker();
                        worker = new BackgroundWorker();
                        BackgroundWorkerLogger logger = new BackgroundWorkerLogger(worker);
                        worker.DoWork += (s, dwe) => {
                            DatabaseContents importContents = newProvider.ReadDatabase();
                            logger.Log("Read database...", StatusMessage.StatusMessageType.Success);
                            DalConfigurationManager.Provider.ImportDatabase(importContents, logger);
                            logger.Log("Done", StatusMessage.StatusMessageType.Success);
                        };
                        worker.RunWorkerCompleted += (s, rwe) => {
                            if(rwe.Error != null) {
                                DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                            }
                            NavigationCommands.Refresh.Execute(null, this);
                        };
                        WinWorkerLog importDialog = new WinWorkerLog(I18nUtils.GetString("Strings", "Working"), worker) {Owner = this};
                        importDialog.ShowDialog();
                        break;
                }
            }
        }

        /// <summary>
        /// Shows an import dialog , then imports or opens a database.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Import(object sender, ExecutedRoutedEventArgs e) {
            Constants.DataType importType = string.IsNullOrEmpty(Convert.ToString(e.Parameter)) ? Constants.DataType.LegacyXML : (Constants.DataType)Enum.Parse(typeof(Constants.DataType), e.Parameter.ToString(), true);
            EncryptionProviders importAlgorithm = EncryptionProviders.None;
            WinImport dlg = new WinImport(importType, importAlgorithm) { Owner = this };
            if(dlg.ShowDialog() == true) {
                string fileName = dlg.FileName;
                IReadOnlyDatabase importProvider = null;
                worker = new BackgroundWorker();
                BackgroundWorkerLogger logger = new BackgroundWorkerLogger(worker);
                switch (dlg.FileType) {
                    case Constants.DataType.LegacyXML:
                        importProvider = new LegacyXMLParser(fileName, logger);
                        break;
                    case Constants.DataType.Keepass2x:
                        importProvider = new Keepass2xParser(fileName, logger);
                        break;
                    case Constants.DataType.Auto:
                    default:
                        try {
                            importProvider = AutomaticParser.GetDatabaseProvider(fileName, null);
                        } catch(Exception ex) {
                            DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Errors", "Error"));
                        }
                        break;
                }

                if(importProvider is IDbProvider) { // if it's a read/write db and supports encryption, it may be passworded
                    var pwValidationResult = PromptForPassword(importProvider as IDbProvider);
                    if(!pwValidationResult.Key) {
                        return;
                    }
                    importProvider = pwValidationResult.Value;
                }
                WaitForWorker();
                worker.DoWork += (s, dwe) => {
                    DatabaseContents importContents = importProvider.ReadDatabase();
                    logger.Log("Read database...", StatusMessage.StatusMessageType.Success);
                    DalConfigurationManager.Provider.AddAccounts(importContents.Accounts, logger);
                    logger.Log("Read accounts...", StatusMessage.StatusMessageType.Success);
                    DalConfigurationManager.Provider.AddGroups(importContents.Groups, logger);
                    logger.Log("Read groups...", StatusMessage.StatusMessageType.Success);
                    DalConfigurationManager.Provider.AddTemplates(importContents.Templates, logger);
                    logger.Log("Read templates...", StatusMessage.StatusMessageType.Success);
                };
                worker.RunWorkerCompleted += (s, rwe) => {
                                                 if(rwe.Error != null) {
                                                     DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                                                 }
                                                 NavigationCommands.Refresh.Execute(null, this);
                                             };
                WinWorkerLog importDialog = new WinWorkerLog(I18nUtils.GetString("Strings", "Working"), worker) {Owner = this};
                importDialog.ShowDialog();
            }
        }

        /// <summary>
        /// Shows a dialog to export the database.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Export(object sender, ExecutedRoutedEventArgs e) {
            string param = Convert.ToString(e.Parameter).ToLowerInvariant();
            if(param == "descriptor") {
                try {
                    string descriptorSavePath;
                    if(DalConfigurationManager.Provider is FileBasedDatabase) {
                        var db = (FileBasedDatabase)DalConfigurationManager.Provider;
                        if(db.FileName.StartsWith(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData))) {
                            // app data is a crap place to save a descriptor to
                            descriptorSavePath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                        } else {
                            // custom path anyway, just save there
                            descriptorSavePath = System.IO.Path.GetDirectoryName(db.FileName);
                        }
                    } else {
                        descriptorSavePath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
                    }
                    LockCryptWPFBLL.SaveDescriptor(DalConfigurationManager.Provider, descriptorSavePath);
                } catch(Exception ex) {
                    DialogBox.ShowAlert(this, "Error generating descriptor: " + ex.Message, I18nUtils.GetString("Errors", "Error"));
                }
                return;
            }
            Constants.DataType exportType = 0;
            EncryptionProviders exportAlgorithm = 0;
            switch(param) {
                case "sqlite":
                    exportType = Constants.DataType.SQLite;
                    exportAlgorithm = EncryptionProviders.Aes256;
                    break;
                case "legacy":
                    exportType = Constants.DataType.Legacy;
                    exportAlgorithm = EncryptionProviders.Aes256;
                    break;
                case "html":
                    exportType = Constants.DataType.HTML;
                    exportAlgorithm = EncryptionProviders.None;
                    break;
                case "csv":
                    exportType = Constants.DataType.CSV;
                    exportAlgorithm = EncryptionProviders.None;
                    break;
                case "xml":
                default:
                    exportType = Constants.DataType.XML;
                    exportAlgorithm = EncryptionProviders.Aes256;
                    break;
            }
            WinExport dlg = new WinExport(exportType, exportAlgorithm) { Owner = this };
            if(dlg.ShowDialog() == true) {
                IDbProvider exportProvider = null;
                IEncryptionService encryption = null;
                IDatabaseExporter genericExportProvider = null; // html, csv etc
                try {
                    encryption = CoreConfigurationManager.GetEncryptionProvider(dlg.EncryptionAlgorithm, dlg.Password);
                    switch(dlg.FileType) {
                        case Constants.DataType.SQLite:
                            exportProvider = new SQLiteProvider(dlg.SelectedFile, encryption);
                            break;
                        case Constants.DataType.Legacy:
                            exportProvider = new LegacyProvider(dlg.SelectedFile) { EncryptionKey = dlg.Password }; // legacy is a unique case
                            break;
                        case Constants.DataType.HTML:
                            genericExportProvider = new HTMLExporter(dlg.SelectedFile);
                            break;
                        case Constants.DataType.CSV:
                            genericExportProvider = new CSVExporter(dlg.SelectedFile);
                            break;
                        case Constants.DataType.XML:
                        default:
                            exportProvider = new XmlProvider(dlg.SelectedFile, encryption);
                            break;
                    }
                    
                    WaitForWorker();
                    worker = new BackgroundWorker();
                    BackgroundWorkerLogger logger = new BackgroundWorkerLogger(worker);
                    worker.DoWork += (s, dwe) => {
                        DatabaseContents contents = DalConfigurationManager.Provider.ReadDatabase();
                        logger.Log("Read database contents", StatusMessage.StatusMessageType.Success);
                        if(genericExportProvider != null) {
                            // html, csv etc
                            genericExportProvider.ExportDatabase(contents);
                        } else if(exportProvider != null) {
                            exportProvider.CreateDatabase();
                            logger.Log("Created database for export", StatusMessage.StatusMessageType.Success);
                            logger.Log("Adding groups...", StatusMessage.StatusMessageType.Status);
                            exportProvider.AddGroups(contents.Groups, logger);
                            logger.Log("Adding accounts...", StatusMessage.StatusMessageType.Status);
                            exportProvider.AddAccounts(contents.Accounts, logger);
                            logger.Log("Adding templates...", StatusMessage.StatusMessageType.Status);
                            exportProvider.AddTemplates(contents.Templates, logger);
                        }
                    };
                    worker.RunWorkerCompleted += (s, rwe) => {
                        SetStatus(null);
                        if(rwe.Error != null) {
                            DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                        } else {
                            NavigationCommands.Refresh.Execute(null, this);
                        }
                    };
                    SetStatus(I18nUtils.GetString("Strings", "Working"));
                    worker.RunWorkerAsync();
                    WaitForWorker();
                } catch(Exception ex) {
                    DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Errors", "Error"));
                }
                WaitForWorker();
                CommandManager.InvalidateRequerySuggested();
            }
        }

        /// <summary>
        /// Shows a dialog to create a new database.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CreateDatabase(object sender, ExecutedRoutedEventArgs e) {
            WinCreateDatabase dlgCreateDb;
            if(e.Parameter == null) {
                dlgCreateDb = new WinCreateDatabase();
            } else {
                IDbProvider provider = e.Parameter as IDbProvider;
                dlgCreateDb = new WinCreateDatabase(provider, true);
            }
            dlgCreateDb.Owner = this;
            if(dlgCreateDb.ShowDialog() == true) {
                NavigationCommands.Refresh.Execute(null, this);
                CommandManager.InvalidateRequerySuggested();
            }

        }

        /// <summary>
        /// Clears all data from the database after confirming.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ClearDatabase(object sender, ExecutedRoutedEventArgs e) {
            bool eraseDatabase = false,
                 eraseSettings = false;
            switch (DialogBox.Show(this, I18nUtils.GetString("Strings", "ClearDatabaseDesc"), I18nUtils.GetString("Strings", "ClearTheDatabase"), DialogBox.DialogBoxIcon.Warning, DialogBoxButton.Custom3,
                                   I18nUtils.GetString("Strings", "ClearDatabase"), I18nUtils.GetString("Strings", "ClearDatabaseAndPreferences"), I18nUtils.GetString("Strings", "Cancel"))) {
                                       case DialogBoxButton.Custom1:
                    eraseDatabase = true;
                                           break;
                                       case DialogBoxButton.Custom2:
                                           eraseDatabase = true;
                                           eraseSettings = true;
                                           break;
            }
            WaitForWorker();
            worker = new BackgroundWorker();
            if(eraseDatabase) {
                worker.DoWork += (s, dwe) => DalConfigurationManager.Provider.ClearDatabase();
            }
            worker.RunWorkerCompleted += (s, rwe) => {
                SetStatus(null);
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                } else {
                    if(eraseSettings) {
                        Settings.Default.Reset();
                        Settings.Default.Save();
                        WPFConfigurationManager.Configure();
                    }
                    NavigationCommands.Refresh.Execute(null, this);
                    CommandManager.InvalidateRequerySuggested();
                }
            };
            SetStatus(I18nUtils.GetString("Strings", "Working"));
            worker.RunWorkerAsync();
            WaitForWorker();
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Reloads the database.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ReloadDatabase(object sender, ExecutedRoutedEventArgs e) {
            ReloadDatabase();
        }

        /// <summary>
        /// Reloads the database.
        /// </summary>
        private void ReloadDatabase() {
            INestable selectedItem = treAccounts.SelectedItem as INestable;
            treAccounts.ItemsSource = null;
            Accounts = null;
            Groups = null; 
            Templates = null;
            if(!DalConfigurationManager.Provider.CanAttemptConnection)
                return;
            WaitForWorker();
            var pwValidationResult = PromptForPassword(DalConfigurationManager.Provider);
            if(!pwValidationResult.Key) {
                Relock(true);
                return; //can't load if invalid password
            }
            DalConfigurationManager.Provider = pwValidationResult.Value;
            worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                var allAccounts = DalConfigurationManager.Provider.GetAccounts().ConvertAll(b => new Account(b)).OrderBy(a=>a.Name).ToList();
                var allGroups = DalConfigurationManager.Provider.GetGroups().ConvertAll(b => new Group(b)).OrderBy(g => g.Name).ToList();
                List<ITemplate> templates = DalConfigurationManager.Provider.GetTemplates();
                dwe.Result = new object[] {
                     allAccounts,
                     allGroups,
                     templates
                };
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                treAccounts.IsEnabled = true;
                SetStatus(null);
                if(rwe.Error != null) {
                    if(rwe.Error is MultipleException) {
                        DialogBox.ShowAlert(this, rwe.Error as MultipleException, I18nUtils.GetString("Errors", "Error"));
                    } else {
                        DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                    }
                } else if(rwe.Result is object[]) {
                    var result = (object[])rwe.Result;
                    this.Accounts = (List<Account>)result[0];
                    this.Groups = (List<Group>)result[1];
                    this.Templates = (List<ITemplate>)result[2];
                    CommandManager.InvalidateRequerySuggested();
                }
                UpdateDbLocation();
                ApplicationCommands.Find.Execute(selectedItem, this);
            };
            treAccounts.IsEnabled = false;
            SetStatus(I18nUtils.GetString("Strings", "Loading"));
            worker.RunWorkerAsync();
            WaitForWorker();
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Prompts for a password.
        /// </summary>
        /// <param name="provider">The database provider.</param>
        /// <returns>
        /// A KeyValuePair whose value is the provider as the user may have modified it, and whose key is:
        /// <c>true</c> if the provider's encryption key is valid, otherwise <c>false</c>.
        /// </returns>
        private KeyValuePair<bool, IDbProvider> PromptForPassword(IDbProvider provider) {
            bool correctPassword = false;
            IDbProvider modifiedProvider = provider;
            // check for correct (possibly empty) password once
            WaitForWorker();
            worker = new BackgroundWorker();
            worker.DoWork += (s, dwe) => {
                dwe.Result = provider.CheckPassword();
            };
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                } else {
                    correctPassword = Convert.ToBoolean(rwe.Result);
                    if(!correctPassword) {
                        // check if current pass is valid
                        WinPromptPassword dlg = new WinPromptPassword(provider) { Owner = this };
                        correctPassword = dlg.ShowDialog() == true;
                        modifiedProvider = dlg.DatabaseProvider;
                    }
                }
            };
            worker.RunWorkerAsync();
            WaitForWorker();
            return new KeyValuePair<bool,IDbProvider>(correctPassword, modifiedProvider);
        }

        /// <summary>
        /// Shows a dialog to change the database password.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ChangePassword(object sender, ExecutedRoutedEventArgs e) {
            WinChangePassword dlg = new WinChangePassword(DalConfigurationManager.Provider) { Owner = this };
            dlg.ShowDialog();
            NavigationCommands.Refresh.Execute(null, this);
        }

        /// <summary>
        /// Restores dummy data to the database.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        protected void AddDummyData(object sender, ExecutedRoutedEventArgs e) {
            WaitForWorker();
            worker = new BackgroundWorker();
            string param = Convert.ToString(e.Parameter).ToLowerInvariant();
            switch(param) {
                case "accounts":
                    worker.DoWork += (s, dwe) => DataCreator.AddDummyData(false);
                    break;
                case "templates":
                    worker.DoWork += (s, dwe) => DataCreator.AddTemplates();
                    break;
            }
            worker.RunWorkerCompleted += (s, rwe) => {
                if(rwe.Error != null) {
                    DialogBox.ShowAlert(this, rwe.Error.Message, I18nUtils.GetString("Errors", "Error"));
                }
                CommandManager.InvalidateRequerySuggested();
                NavigationCommands.Refresh.Execute(null, this);
            };
            worker.RunWorkerAsync();
            WaitForWorker();
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Relocks the database.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void FireRelock(object sender, ExecutedRoutedEventArgs e) {
            Relock(Convert.ToBoolean(e.Parameter));
            NavigationCommands.Refresh.Execute(null, this);
        }

        /// <summary>
        /// Relocks the database.
        /// </summary>
        /// <param name="closeDatabase"><c>true</c> if the database should be closed, <c>false</c> to use the user setting when relocking.</param>
        private void Relock(bool closeDatabase) {
            if(Settings.Default.CloseOnLock || closeDatabase) {
                DalConfigurationManager.Provider = new NullDbProvider();
            } else {
                DalConfigurationManager.EncryptionKey = null;
            }
            UpdateDbLocation();
        }
        #endregion

        /// <summary>
        /// Allows a command to execute if the database connection is valid.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void IsDbValid(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && (worker == null || !worker.IsBusy) && DalConfigurationManager.Provider.IsConnected;
        }

        /// <summary>
        /// Always returns true.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void CanAlwaysExecute(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = true;
        }

        /// <summary>
        /// Saves the preferences.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void SavePreferences(object sender, RoutedEventArgs e) {
            Settings.Default.Save();
            WPFConfigurationManager.Configure();
        }

        /// <summary>
        /// Configures the auto-lock timer and other variables from settings.
        /// </summary>
        private void ReloadSettings() {
            ConfigureLockTimer();
            SecureClipboard.ConfigureTimer();
            if(Settings.Default.FirefoxEnabled) {
                FirefoxServer.Instance.Start();
                imgFirefoxStatus.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/Images/firefox_16.png"));
                imgFirefoxStatus.ToolTip = I18nUtils.GetString("Strings", "FirefoxExtensionEnabled");
                imgFirefoxStatus.IsEnabled = true;
            } else {
                FirefoxServer.Instance.Stop();
                imgFirefoxStatus.Source = new BitmapImage(new Uri("pack://application:,,,/Resources/Images/firefox_16.png"));
                imgFirefoxStatus.ToolTip = I18nUtils.GetString("Strings", "FirefoxExtensionDisabled");
                imgFirefoxStatus.IsEnabled = false;
            }
            try {
                if(Settings.Default.MinimizeToTray) {
                    MinimizeToTray.EnableMinimizeToTray(this);
                } else {
                    MinimizeToTray.DisableMinimizeToTray(this);
                }
            } catch(Exception ex) {
                DialogBox.ShowAlert(this, ex.Message, I18nUtils.GetString("Strings", "MinimizeToTray"));
            }
        }
        
        /// <summary>
        /// Updates and saves the 'Always on top' preference.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void SaveAlwaysOnTop(object sender, RoutedEventArgs e) {
            Settings.Default.AlwaysOnTop = itmTopMost.IsChecked == true;
            Settings.Default.Save();
        }

        /// <summary>
        /// Shows a dialog to synchronize with a mobile device.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Synchronize(object sender, ExecutedRoutedEventArgs e) {
            WinSynchronize dlg = new WinSynchronize() { Owner = this };
            dlg.Show();
            dlg.Closed += (s, ce) => NavigationCommands.Refresh.Execute(null, this);
        }

        /// <summary>
        /// Changes the current view.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ChangeView(object sender, ExecutedRoutedEventArgs e) {
            string[] viewNames = Enum.GetNames(typeof(WPFConstants.AccountViews));
            if(e.Parameter == null) {
                WPFConstants.AccountViews newView = accountView.CurrentView + 1;
                if((int)newView >= viewNames.Length) {
                    newView = 0; // loop back to beginning
                }
                accountView.CurrentView = newView;
            } else {
                Settings.Default.PrefferedView = e.Parameter.ToString();
                Settings.Default.Save();
            }
            NavigationCommands.Refresh.Execute(null, this);
            CommandManager.InvalidateRequerySuggested();
        }

        /// <summary>
        /// Selects a node on right click
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void tre_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e) {
            IInputElement element = treAccounts.InputHitTest(e.GetPosition(treAccounts));
            while(!((element is TreeView) || element == null)) {
                if(element is TreeViewItem)
                    break;
                if(element is FrameworkElement) {
                    FrameworkElement fe = (FrameworkElement)element;
                    element = (IInputElement)(fe.Parent ?? fe.TemplatedParent);
                } else {
                    break;
                }
            }
            if(element is TreeViewItem) {
                element.Focus();
                e.Handled = true;
            }
        }

        /// <summary>
        /// Fires the edit command when a node is double clicked.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void tre_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
            if(treAccounts.SelectedItem is IGroup && (treAccounts.SelectedItem as IGroup).Children.Count > 0)
                return;
            ApplicationCommands.Properties.Execute(treAccounts.SelectedItem, this);
        }

        /// <summary>
        /// Changes the selected item when a new item is double clicked in the account view.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="string"/> instance containing the event data.</param>
        private void accountView_RequestNavigate(object sender, RoutedPropertyChangedEventArgs<INestable> e) {
            if(e.NewValue == null)
                return;
            WPFUtils.SetSelectedItem(treAccounts, e.NewValue);
            if(treAccounts.SelectedItem == null) {
                TreeViewItem firstItem = treAccounts.Items[0] as TreeViewItem;
                firstItem.IsSelected = true;
                firstItem.Focus();
            }
        }

        /// <summary>
        /// Opens the browser to the donate page.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void btnDonate_Click(object sender, RoutedEventArgs e) {
            System.Diagnostics.Process.Start("http://www.lockcrypt.com/donate");
        }

        /// <summary>
        /// Closes the window.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void CloseWindow(object sender, ExecutedRoutedEventArgs e) {
            Close();
        }

        /// <summary>
        /// Shows the help file.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void ShowHelp(object sender, ExecutedRoutedEventArgs e) {
            WPFUtils.OpenHelp(null);
        }

        /// <summary>
        /// Updates or hides the status text for the current database.
        /// </summary>
        private void UpdateDbLocation() {
            //if(DalConfigurationManager.Provider.GetType() == typeof(NullDbProvider)) {
            if(DalConfigurationManager.Provider == null || !DalConfigurationManager.Provider.IsConnected) {
                pnlCurrentDB.Visibility = Visibility.Collapsed;
            } else {
                pnlCurrentDB.Visibility = Visibility.Visible;
                txtCurrentDBPath.Text = DalConfigurationManager.Provider.FormattedName;
            }
        }

        /// <summary>
        /// Sets the text on the status message (null hides the statusbar and label).
        /// </summary>
        /// <param name="message">The message.</param>
        private void SetStatus(string message) {
            if(message == null) {
                prgStatus.Visibility = Visibility.Collapsed;
                lblStatus.Visibility = Visibility.Collapsed;
            } else {
                prgStatus.Visibility = Visibility.Visible;
                lblStatus.Visibility = Visibility.Visible;
                lblStatus.Text = message;
            }
        }

        /// <summary>
        /// Determines whether either an item is selected or the command parameter is an INestable.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void IsItemSelected(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && (treAccounts.SelectedItem is INestable || e.Parameter is INestable);
        }

        /// <summary>
        /// Determines whether the BackgroundWorker is busy.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void IsWorkerBusy(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && (worker == null || !worker.IsBusy) && DalConfigurationManager.Provider.IsConnected;
        }

        /// <summary>
        /// Waits for the BackgroundWorker to complete before returning.
        /// </summary>
        private void WaitForWorker() {
            if(worker != null && worker.IsBusy) {
                while(worker.IsBusy) {
                    Dispatcher.Invoke(DispatcherPriority.Background, new ThreadStart(delegate() { }));
                }
            }
        }

        #region Search and Filter
        /// <summary>
        /// Filters the item source based on selected values.
        /// </summary>
        private void FilterTree() {
            FilterTree(treAccounts.SelectedItem as INestable);
        }

        /// <summary>
        /// Filters the tree according to the current search filters.
        /// </summary>
        /// <param name="selectedItem">The selected item.</param>
        private void FilterTree(INestable selectedItem) {
            if(this.Accounts == null || this.Groups == null)
                return;
            bool applyFilter = expSearch.IsExpanded && (lstFilterFields.HasItems || txtFilterName.Text.Length > 0
                                                        || chkAllTemplates.IsChecked != true);
            List<Account> accountsToAdd = this.Accounts.Select(a => a.Clone()).Cast<Account>().ToList();
            List<Group> groupsToAdd = this.Groups.Select(a => a.Clone()).Cast<Group>().ToList();

            string itemName = txtFilterName.Text.Trim(),
                    templateId = (cboFilterTemplate.SelectedValue ?? string.Empty).ToString();
            IEnumerable<Account> filteredAccounts;
            IEnumerable<WildcardField> fields = lstFilterFields.Items.Cast<WildcardField>();
            if(applyFilter) { // only filter if there's at least one thing to filter by
                filteredAccounts = accountsToAdd.Where(a => {
                    bool valid = false;

                    if(itemName.Length == 0) { // filter field name if a filter is set
                        valid = true;
                    } else {
                        switch(cboFilterName.SelectedIndex) {
                            case 0: // contains
                                valid = a.Name.IndexOf(itemName, StringComparison.InvariantCultureIgnoreCase) >= 0;
                                break;
                            case 1: // starts with
                                valid = a.Name.StartsWith(itemName, StringComparison.InvariantCultureIgnoreCase);
                                break;
                            case 2: // ends with
                                valid = a.Name.EndsWith(itemName, StringComparison.InvariantCultureIgnoreCase);
                                break;
                        }
                    }

                    if(valid) { // good so far - filter template
                        if(chkAllTemplates.IsChecked == true) {
                            valid = true;
                        } else {
                            valid = a.Template.ID == templateId;
                        }
                    }

                    if(valid && fields.Any()) {
                        foreach(WildcardField field in fields) {
                            if(field.FieldName != null && !a.Fields.Any(f => field.FieldName.Matches(f.Name)) ||
                               field.FieldValue != null && !a.Fields.Any(f => field.FieldValue.Matches(f.Value))) {
                                valid = false;
                                break;
                            }
                        }
                    }
                    return valid;
                });
            } else {
                filteredAccounts = accountsToAdd;
            }
            IEnumerable<INestable> topLevelGroups = LockCryptBLL.ArrangeGroups(groupsToAdd.Cast<IGroup>(), filteredAccounts.Cast<IAccount>());
            ObservableCollection<INestable> topLevelItems = new ObservableCollection<INestable>(topLevelGroups);

            if(chkEmptyGroups.IsChecked != true) {
                for(int j = topLevelItems.Count - 1; j >= 0; j--) {
                    IGroup itm = topLevelItems[j] as IGroup;
                    if(itm != null) {
                        LockCryptBLL.RemoveEmptyGroups(itm);
                    }
                    if(!itm.Children.Any()) {
                        topLevelItems.RemoveAt(j);
                    }
                }
            }
            treAccounts.ItemsSource = topLevelItems;

            Console.WriteLine("{0} accounts, {1} groups, {2} total", this.Accounts.Count(), this.Groups.Count(), topLevelItems.Count);
            if(topLevelItems.Count > 0) {
                var equivalentItem = selectedItem == null ? topLevelItems.FirstOrDefault() : topLevelItems.FirstOrDefault(i=>i.ID == selectedItem.ID);
                if(selectedItem != null) {
                    WPFUtils.SetSelectedItem(treAccounts, selectedItem);
                }
                if(treAccounts.SelectedItem == null) {
                    TreeViewItem firstItem = treAccounts.ItemContainerGenerator.ContainerFromItem(equivalentItem) as TreeViewItem;
                    if(firstItem != null) {
                        firstItem.IsSelected = true;
                        //firstItem.Focus();
                    }
                }
            }
        }

        /// <summary>
        /// Refreshes or clears the filters when the Find command is fired.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void Search(object sender, ExecutedRoutedEventArgs e) {
            //            if(!Settings.Default.SearchPaneVisible) {
            //              Settings.Default.SearchPaneVisible = true;
            //        } else {
            string param = (e.Parameter ?? string.Empty).ToString().ToLowerInvariant();
            if(param == "toggle" && !Settings.Default.SearchPaneVisible) {
                Settings.Default.SearchPaneVisible = true;
            } else if(param == "clear") {
                cboFilterName.SelectedIndex = 0;
                txtFilterName.Text = string.Empty;
                lstFilterFields.Items.Clear();
                lstFilterFields.SelectedIndex = -1;
                chkAllTemplates.IsChecked = true;
            }
            FilterTree(e.Parameter as INestable);
        }

        /// <summary>
        /// Starts the search timer and saves the 'Live search' preferences.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.RoutedEventArgs"/> instance containing the event data.</param>
        private void chkLiveFilter_Checked(object sender, RoutedEventArgs e) {
            if(!IsLoaded)
                return;
            Settings.Default.Save();
            StartSearchTimer();
        }

        /// <summary>
        /// Starts the search timer if 'Live Filters' is checked.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void UpdateFilters(object sender, EventArgs e) {
            if(!this.IsLoaded || (chkLiveFilter.IsChecked != true)) {
                return;
            }
            StartSearchTimer();
        }

        /// <summary>
        /// Starts the search timer.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.KeyEventArgs"/> instance containing the event data.</param>
        private void txt_KeyUp(object sender, KeyEventArgs e) {
            if(chkLiveFilter.IsChecked == true && IsTextChangingKey(e.Key)) {
                StartSearchTimer();
            }
        }

        /// <summary>
        /// Determines whether the specified key is a backspace or delete key.
        /// </summary>
        /// <param name="key">The key.</param>
        private bool IsTextChangingKey(Key key) {
            if(key == Key.Back || key == Key.Delete) {
                return true;
            } else {
                KeyConverter conv = new KeyConverter();
                string keyString = (string)conv.ConvertTo(key, typeof(string));

                return keyString.Length == 1;
            }
        }

        /// <summary>
        /// Starts the suggestion timer or filters immediately if the interval is zero.
        /// </summary>
        private void StartSearchTimer() {
            if(Settings.Default.SearchDelay > 0) {
                _delayTimer.Interval = Settings.Default.SearchDelay;
                _delayTimer.Start();
            } else {
                FilterTree();
            }
        }

        /// <summary>
        /// Invokes the filter method when the search timer elapses.
        /// </summary>
        /// <param name="source">The source.</param>
        /// <param name="e">The <see cref="System.Timers.ElapsedEventArgs"/> instance containing the event data.</param>
        private void SearchTimerElapsed(object source, System.Timers.ElapsedEventArgs e) {
            _delayTimer.Stop();
            Dispatcher.BeginInvoke(new FilterDelegate(this.FilterTree));
        }

        #region Field Filters
        /// <summary>
        /// Adds a new field filter.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void AddFieldFilter(object sender, ExecutedRoutedEventArgs e) {
            WinAddFieldFilter dlg = new WinAddFieldFilter() { Owner = this };
            if(dlg.ShowDialog() == true) {
                lstFilterFields.Items.Add(dlg.CurrentField);
                if(chkLiveFilter.IsChecked == true) {
                    StartSearchTimer();
                }
            }
        }

        /// <summary>
        /// Edits the selected field filter.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void EditFieldFilter(object sender, ExecutedRoutedEventArgs e) {
            WildcardField field = e.Parameter as WildcardField;
            if(field == null)
                return;
            WinAddFieldFilter dlg = new WinAddFieldFilter(field.Clone()) { Owner = this };
            if(dlg.ShowDialog() == true) {
                int selected = lstFilterFields.SelectedIndex;
                lstFilterFields.Items.RemoveAt(selected);
                lstFilterFields.Items.Insert(selected, dlg.CurrentField);
                if(chkLiveFilter.IsChecked == true) {
                    StartSearchTimer();
                }
            }
        }

        /// <summary>
        /// Edits a field when it's double clicked.
        /// </summary>
        /// <param name="sender">The source of the event.</param>
        /// <param name="e">The <see cref="System.Windows.Input.MouseButtonEventArgs"/> instance containing the event data.</param>
        private void lstFilterFields_MouseDoubleClick(object sender, MouseButtonEventArgs e) {
            WildcardField selectedItem = lstFilterFields.SelectedItem as WildcardField;
            if(selectedItem != null) {
                LockCryptCommands.Edit.Execute(selectedItem, this);
            }
        }

        /// <summary>
        /// Determines whether a field filter is selected in the list.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.CanExecuteRoutedEventArgs"/> instance containing the event data.</param>
        private void IsFieldFilterSelected(object sender, CanExecuteRoutedEventArgs e) {
            e.CanExecute = IsLoaded && lstFilterFields.SelectedItem is WildcardField; 
        }

        /// <summary>
        /// Deletes the selected field filter.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Windows.Input.ExecutedRoutedEventArgs"/> instance containing the event data.</param>
        private void DeleteFieldFilter(object sender, ExecutedRoutedEventArgs e) {
            int selected = lstFilterFields.SelectedIndex;
            lstFilterFields.Items.RemoveAt(selected);
            if(selected < lstFilterFields.Items.Count - 1) {
                lstFilterFields.SelectedIndex = selected;
            } else {
                lstFilterFields.SelectedIndex = lstFilterFields.Items.Count - 1;
            }
            if(chkLiveFilter.IsChecked == true) {
                StartSearchTimer();
            }
        }
        #endregion
        #endregion
    }
}
